package com.shermanzhou.springbootflowable.demo;

import com.alibaba.fastjson.JSONObject;
import com.shermanzhou.springbootflowable.demo.flow.ApprovalFlow;
import com.shermanzhou.springbootflowable.demo.flow.ApprovalFlowNode;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zhouchaoqun02
 * @date 2020-06-14 19:55
 */
@SpringBootTest
public class ApprovalFlowTest3 {


    @Autowired
    @Qualifier("normalProcessEngine")
    private ProcessEngine normalEngine;

    @Autowired
    @Qualifier("simulateProcessEngine")
    private ProcessEngine simulateEngine;

    @Test
    public void finishedProcInstFlow() {
        String procInstId = "7501";
        HistoricProcessInstance historicProcessInstance = normalEngine.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();

        List<HistoricTaskInstance> hisTasks = normalEngine.getHistoryService().createHistoricTaskInstanceQuery().includeProcessVariables().includeTaskLocalVariables().processInstanceId(procInstId).list();

        List<HistoricActivityInstance> actHistoryList = normalEngine.getHistoryService().createHistoricActivityInstanceQuery().processInstanceId(procInstId).list();
        Process process = normalEngine.getRepositoryService().getBpmnModel(historicProcessInstance.getProcessDefinitionId()).getProcesses().get(0);

        long t1 = System.currentTimeMillis();
        Map<String, List<TaskInfo>> actIdAndHisTasksMap = hisTasks.stream().collect(Collectors.groupingBy(TaskInfo::getTaskDefinitionKey));
        Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap = actHistoryList.stream()
                .collect(Collectors.groupingBy(HistoricActivityInstance::getActivityId));


        ApprovalFlow approvalFlow = makeUpRootApprovalFlow(process, actIdAndActivitiesMap, actIdAndHisTasksMap);
        long t2 = System.currentTimeMillis();

        System.out.println(JSONObject.toJSONString(approvalFlow));


        System.out.println("execute cost: " + (t2 - t1));
    }

    private ApprovalFlow makeUpRootApprovalFlow(Process process,
                                                Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap,
                                                Map<String, List<TaskInfo>> actIdAndHisTasksMap) {
        FlowNode startNode = (FlowNode) process.getInitialFlowElement();
        HistoricActivityInstance startAct = actIdAndActivitiesMap.get(startNode.getId()).get(0);
        ApprovalFlowNode root = ApprovalFlowNode.builder()
                .nodeId(startAct.getActivityId())
                .nodeName(startAct.getActivityName())
                .build();
        Map<SubProcess, ApprovalFlowNode> subProcessCollector = new HashMap<>();
        makeUpNextNodes(root, actIdAndActivitiesMap, process.getFlowElementMap(), actIdAndHisTasksMap, subProcessCollector);
        makeUpSubPrcessApprovalFlow(subProcessCollector, actIdAndActivitiesMap, actIdAndHisTasksMap);
        return ApprovalFlow.builder().approvalFlowNode(root).flowType(ApprovalFlow.ApprovalFlowTypeEnum.ROOT).build();
    }

    private List<ApprovalFlow> makeUpSubApprovalFlow(ApprovalFlowNode subProcessNode,
                                                     SubProcess subProcess,
                                                     Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap,
                                                     Map<String, List<TaskInfo>> actIdAndHisTasksMap) {
        List<ApprovalFlow> subFlows = new ArrayList<>();

        FlowNode startNode = (FlowNode) subProcess.getFlowElements().stream()
                .filter(flowElement -> flowElement instanceof StartEvent)
                .findFirst().orElseThrow(() -> new RuntimeException("subProcess start event not found"));
        List<HistoricActivityInstance> startActs = actIdAndActivitiesMap.get(startNode.getId());

        startActs.forEach(startAct -> {
            ApprovalFlowNode root = ApprovalFlowNode.builder()
                    .nodeId(startAct.getActivityId())
                    .nodeName(startAct.getActivityName() != null ? startAct.getActivityName() : startAct.getActivityType())
                    .collectionVariable(subProcessNode.getCollectionVariable())
                    .elementVariable(subProcessNode.getElementVariable())
                    .elementVariableValues(subProcessNode.getElementVariableValues())
                    .executionId(startAct.getExecutionId())
                    .build();
            Map<SubProcess, ApprovalFlowNode> subProcessCollector = new HashMap<>();
            makeUpNextNodes(root, actIdAndActivitiesMap, subProcess.getFlowElementMap(), actIdAndHisTasksMap, subProcessCollector);
            makeUpSubPrcessApprovalFlow(subProcessCollector, actIdAndActivitiesMap, actIdAndHisTasksMap);
            subFlows.add(ApprovalFlow.builder().approvalFlowNode(root).flowType(ApprovalFlow.ApprovalFlowTypeEnum.SUB).build());
        });

        return subFlows;
    }

    private void makeUpSubPrcessApprovalFlow(Map<SubProcess, ApprovalFlowNode> subProcessCollector,
                                             Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap,
                                             Map<String, List<TaskInfo>> actIdAndHisTasksMap) {
        subProcessCollector.forEach((subProcess, subProcessNode) -> {
            List<ApprovalFlow> approvalFlows = makeUpSubApprovalFlow(subProcessNode, subProcess, actIdAndActivitiesMap, actIdAndHisTasksMap);
            subProcessNode.setSubFlows(approvalFlows);
        });
    }

    private void makeUpNextNodes(ApprovalFlowNode curNode,
                                 Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap,
                                 Map<String, FlowElement> flowElementMap,
                                 Map<String, List<TaskInfo>> actIdAndHisTasksMap,
                                 Map<SubProcess, ApprovalFlowNode> subProcessCollector) {
        FlowNode flowNode = (FlowNode) flowElementMap.get(curNode.getNodeId());
        List<FlowNode> nextNodes = getNextActiveNodes(flowNode, actIdAndActivitiesMap);
        if (nextNodes == null || nextNodes.size() == 0) {
            return;
        }

        for (FlowNode nextNode : nextNodes) {
            if (nextNode instanceof SubProcess) {
                SubProcess subProcess = (SubProcess) nextNode;
                Set<String> executionIds = getSubProcessExecutionIds(actIdAndActivitiesMap, subProcess);
                MultiInstanceLoopCharacteristics loopCharacteristics = subProcess.getLoopCharacteristics();
                List<Object> values = getElementVariableValues("7501", loopCharacteristics, executionIds);
                ApprovalFlowNode node = ApprovalFlowNode.builder()
                        .nodeId(nextNode.getId())
                        .nodeName(nextNode.getName())
                        .collectionVariable(loopCharacteristics.getInputDataItem())
                        .elementVariable(loopCharacteristics.getElementVariable())
                        .elementVariableValues(values)
                        .build();
                subProcessCollector.put((SubProcess) nextNode, node);
                curNode.addNextNode(node);
            } else {
                String executionId = null;
                List<HistoricActivityInstance> acts = actIdAndActivitiesMap.get(nextNode.getId());
                if (acts != null && acts.size() != 0) {
                    executionId = acts.get(0).getExecutionId();
                }
                ApprovalFlowNode node = ApprovalFlowNode.builder()
                        .nodeId(nextNode.getId())
                        .nodeName(nextNode.getName())
                        .executionId(executionId)
                        .build();
                findAndCompleteTask(node, actIdAndHisTasksMap);
                curNode.addNextNode(node);
            }
        }
        curNode.getNextNodes().forEach(cn -> makeUpNextNodes(cn, actIdAndActivitiesMap, flowElementMap, actIdAndHisTasksMap, subProcessCollector));
    }

    private Set<String> getSubProcessExecutionIds(Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap, SubProcess subProcess) {
        Set<String> executionIds = new HashSet<>();
        List<HistoricActivityInstance> acts = actIdAndActivitiesMap.get(subProcess.getId());
        if (acts != null && acts.size() != 0) {
            executionIds = acts.stream().map(HistoricActivityInstance::getExecutionId).collect(Collectors.toSet());
        }
        return executionIds;
    }

    private List<Object> getElementVariableValues(String procInstId, MultiInstanceLoopCharacteristics loopCharacteristics, Set<String> executionIds) {
        List<HistoricVariableInstance> elementValues = normalEngine.getHistoryService().createHistoricVariableInstanceQuery()
                .processInstanceId(procInstId)
                .executionIds(executionIds)
                .variableName(loopCharacteristics.getElementVariable())
                .list();
        return elementValues.stream().map(HistoricVariableInstance::getValue).collect(Collectors.toList());
    }

    private void findAndCompleteTask(ApprovalFlowNode node, Map<String, List<TaskInfo>> actIdAndHisTasksMap) {
        List<TaskInfo> taskInfos = actIdAndHisTasksMap.get(node.getNodeId());
        if (taskInfos == null || taskInfos.size() == 0) {
            return;
        }
        if (node.getExecutionId() != null) {
            taskInfos = taskInfos.stream().filter(task -> task.getExecutionId().equals(node.getExecutionId())).collect(Collectors.toList());
        }
        node.setTaskEntities(taskInfos);
    }

    private List<FlowNode> getNextActiveNodes(FlowNode flowNode, Map<String, List<HistoricActivityInstance>> actIdAndActivitiesMap) {
        List<FlowNode> activeNodes = new ArrayList<>();
        for (SequenceFlow out : flowNode.getOutgoingFlows()) {
            FlowNode targetFlowElement = (FlowNode) out.getTargetFlowElement();
            if (actIdAndActivitiesMap.containsKey(targetFlowElement.getId())) {
                activeNodes.add(targetFlowElement);
            }
        }

        List<FlowNode> result = new ArrayList<>();
        for (FlowNode node : activeNodes) {
            if (node instanceof Gateway) {
                List<FlowNode> nodesComeFromGateway = getNextActiveNodes(node, actIdAndActivitiesMap);
                result.addAll(nodesComeFromGateway);
            } else {
                result.add(node);
            }
        }
        return result;
    }

}
